#import <defaults/defaults.h>    //for NXArgv
#import <appkit/Application.h> //for NXApp
#import <appkit/Listener.h>    //for void NXUpdateDynamicServices(void)
#import <appkit/Panel.h>       //for NXRunAlertPanel
#import <appkit/Pasteboard.h>
#import <libc.h>               //for mktemp
#import <mach/mach.h>               //vm_deallocate
#import <objc/Storage.h>
#import <stdio.h>
#import <stdlib.h>
#import <strings.h>            //For strcmp
#import <sys/dir.h>
#import <sys/stat.h>
#import <sys/param.h>          //For MAXPATHLEN
//Maybe not using <appkit/appkit.h> goes faster... But it's a pain spelling
//all this out. I want a CASE tool from NeXT!

#import "Piper.h"
@implementation Piper
-appDidInit:sender{
  // Make us the object that service requests (piperMessage) get sent to.
    [[NXApp appListener] setServicesDelegate:self];
  return self;
  }
-piperMessage:(id)pb userData:(const char *)userData error:(char **)msg{
  /*var*/
    char        **pbtypes,*data,*outputData,
                scriptCmd[MAXPATHLEN];
    int         length,err, i, outputLen, outputMaxlen;
    FILE        *pipe;
    NXStream    *pNXStream;
    static char outputFile[]="/tmp/PipeXXXXXX";
    struct stat sbuf;
  if(!strcmp(userData,"NXUpdateDynamicServices")){
    [self reloadScripts:self];
    return self; 
    }
  for(pbtypes=(char **)[pb types];*pbtypes;pbtypes++){
    if(   (*pbtypes==NXAsciiPboardType    /*Plain ASCII text*/)||
          (*pbtypes==NXFilenamePboardType /*ASCII file name */)){
      [pb readType:*pbtypes data:&data length:&length];
      sprintf(scriptCmd,"%s/script",userData);
      stat(scriptCmd, &sbuf);
      if(!(sbuf.st_mode & 0111)){  
        NXRunAlertPanel(NULL,"No execute permission on %s", 
                        NULL,NULL,NULL,                scriptCmd);
        exit(1);
        }
      mktemp(outputFile);
      if(0){
        }else if(*pbtypes==NXAsciiPboardType){
          //For ascii pasteboard types, we just read the selection
          //and pipe it through the given script. The output is collected
          //in a temporary file.
          //I think this should become an NXStream, somehow
          //(needs another thread, hmmm)!!!
          sprintf(scriptCmd,"%s/script >%s 2>/dev/console",
                  userData,outputFile);
          if((pipe=popen(scriptCmd,"w"))==NULL){
            NXRunAlertPanel(NULL, "Command \"%s\" failed", 
                            NULL,NULL,NULL,scriptCmd);
            exit(1);
            }
          for(i=0;i<length;i++) putc(data[i], pipe);
          if(err=pclose(pipe)){
            NXRunAlertPanel(NULL,"Command \"%s\" returned error code %d", 
                            NULL,NULL,NULL, userData,                err);
            }
        }else if(*pbtypes==NXFilenamePboardType){
          //Selection is a filename. Run the script with the filename
          //as the only parameter. Any output is collected in the
          //temporary file.
          /*var*/ char *pchar;
          while(pchar=data,pchar){
            if(data=index(data,'\t')){ *data=0; data++; }
            sprintf(scriptCmd, "%s/script \"%s\" >%s 2>/dev/console",
                    userData, pchar, outputFile);
            if(err=system(scriptCmd)){
              NXRunAlertPanel(NULL,"Command \"%s\" returned error code %d. "
                                   "Look at Console", 
                              NULL,NULL,NULL, userData,                err);
              }
            }
        }
      //Now the fun bit. Any output from the script will be in the temporary
      //file outputFile. Map the file in and paste the data out to the
      //selection pasteboard.
      pNXStream=NXMapFile(outputFile,NX_READONLY);
      NXGetMemoryBuffer(pNXStream, &outputData, &outputLen, &outputMaxlen);
      [pb declareTypes:&NXAsciiPboardType num:1 owner:self];
      [pb writeType:NXAsciiPboardType data:outputData length:outputLen];
      vm_deallocate(task_self(),(int)data,length);
      sprintf(scriptCmd,"rm -f %s",outputFile);
      system(scriptCmd);
      return self;
      }
    }
  //Didn't find one we liked (dropped out of pbtypes list).
  NXRunAlertPanel(NULL,"Piper does not recognize pasteboard type %d", 
                  NULL,NULL,NULL,*pbtypes);
  return self;
  }
-reloadScripts:sender{
  /*var*/
    int keyFound,returnFound;
    struct stat stats;
    char option[256],keyOption[256],returnOption[256];
    FILE *lspipe,*serviceFile,*optionsFile;
  /*Make serviceFile your private services cache file.*/{
    /*var*/ char serviceFilePath[MAXPATHLEN];
    sprintf(serviceFilePath,"%s/.NeXT/services/Piper",getenv("HOME"));
    if((serviceFile=fopen(serviceFilePath,"w"))==NULL){
      NXRunAlertPanel(NULL,"Could not open service file %s",
                      "Quit",NULL,NULL,                 serviceFilePath);
      exit(1);
      } //Shouldn't happen.
    }
  /*Make lspipe a pipe filled with script paths.*/{
    /*var*/ char  executablePath[MAXPATHLEN],
                  cmd           [MAXPATHLEN];
    if(NXArgv[0][0]=='/'){
        strcpy(executablePath,NXArgv[0]); *rindex(executablePath,'/')=0;
      }else{
        switch(NXRunAlertPanel( NULL,
                                "Please don't use the Update Dynamic Services "
                                "command in Piper's menu if it were "
                                "launched "
                                "from a shell with a partial path if you want "
                                "its SampleScripts to be included. "
                                "Maybe some programmer will give me a tool "
                                "to get the full path using the notion of "
                                "current directory or something? "
                                "He might then distribute this to the NeXT "
                                "programming community¼",
                                "Quit","Cancel",NULL)){
          case NX_ALERTDEFAULT: [NXApp terminate:self]; //break's not necessary
          case NX_ALERTALTERNATE: return self;          //same
          }
      }
    sprintf(cmd,"ls %s/SampleScripts/../SampleScripts 2>/dev/null "
                "| awk '{print \"%s/SampleScripts/\"$0}' 2>/dev/null; "
                "ls /LocalLibrary/Piper/../Piper 2>/dev/null "
                "| awk '{print \"/LocalLibrary/Piper/\"$0}' 2>/dev/null; "
                "ls %s/Library/Piper/../Piper 2>/dev/null "
                "| awk '{print \"%s/Library/Piper/\"$0}' 2>/dev/null",
            executablePath, executablePath,getenv("HOME"),getenv("HOME"));
    if((lspipe=popen(cmd,"r"))==NULL){
      NXRunAlertPanel(NULL,"lspipe could not be opened",
                      NULL,NULL,NULL);
      exit(1);
      } //If this happens, it's a bug.
    }
  /*Search for scripts.*/{
    /*var*/ int count; //how many valid ones we found
            char scriptDirPath[MAXPATHLEN],actualScriptPath[MAXPATHLEN];
    for(count=0;
        fgets(scriptDirPath,sizeof(scriptDirPath),lspipe)!=NULL;
        count++){
      scriptDirPath[strlen(scriptDirPath)-1]=0;
      if(stat(scriptDirPath,&stats)<0||!(stats.st_mode&S_IFDIR)){
        NXRunAlertPanel(NULL,"%s is not a directory!",
                        NULL,NULL,NULL,scriptDirPath);
        continue;
        }
      sprintf(actualScriptPath,"%s/script",scriptDirPath);
      if(stat(actualScriptPath,&stats)<0||!(stats.st_mode & 0111)){
        NXRunAlertPanel(NULL,"%s does not exist or is not executable", 
                        NULL,NULL,NULL,actualScriptPath);
        continue;
        }
      fprintf(serviceFile,"Message: piperMessage\n"
                          "Port: Piper\n"
                          "Menu Item: Piper/%s\n"
                          "User Data: %s\n",
              rindex(scriptDirPath,'/')+1,
              scriptDirPath);
      /*Look for options.*/{
        /*var*/ char optionsPath[MAXPATHLEN];
        sprintf(optionsPath,"%s/options",scriptDirPath);
        keyFound=returnFound=0;
        if((optionsFile=fopen(optionsPath,"r"))!=NULL){
          while(fgets(option,sizeof(option),optionsFile)!=NULL){
            if(!strncmp(option,"Key Equivalent",14)){
                strcpy(keyOption,option);
                keyFound++;
              }else if(!strncmp(option,"Return Type",11)){
                strcpy(returnOption, option);
                returnFound++;
              }else if(!strcmp(option,"Send Type: NXAsciiPboardType\n")){
                fprintf(serviceFile,option);
              }else if(!strcmp(option,"Send Type: NXFilenamePboardType\n")){
                fprintf(serviceFile,option);
              }else{
                NXRunAlertPanel(NULL,"Option\n"
                                     "%s\n"
                                     "not recognized in script\n"
                                     "%s",
                                NULL,NULL,NULL,option,scriptDirPath);
              }
            }
          fclose(optionsFile);
          }
        }
      if(returnFound) fprintf(serviceFile,returnOption);
      if(keyFound)    fprintf(serviceFile,keyOption);
      /*always*/      fprintf(serviceFile,"\n");
      }
    pclose(lspipe);
    fclose(serviceFile);
    NXUpdateDynamicServices();
    if(!count){
      /*var*/ char somePath[MAXPATHLEN];
      NXRunAlertPanel(NULL,"No valid scripts found",
                      NULL,NULL,NULL);
      sprintf(somePath,"%s/.NeXT/services/Piper",getenv("HOME"));
      if(remove(somePath))
        NXRunAlertPanel(NULL,
          "Also, I could not get rid of ``%s''",
          NULL,NULL,NULL,somePath);
      exit(1);
      }
    }
  NXRunAlertPanel(NULL,
                  "Relaunch applications in which you want to use new "
                  "names, if you haven't got a version (3.0 at least) "
                  "in which NeXT fixed this.",
                  NULL,NULL,NULL);
  return self;
  }
@end
